package uk.ac.open.kmi.dyniqx.storages;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.TreeSet;

import org.apache.lucene.search.Hits;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.TermFreqVector;
import org.apache.lucene.document.Document;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.analysis.standard.StandardAnalyzer;

import org.apache.log4j.Logger;
import org.directwebremoting.WebContext;
import org.directwebremoting.proxy.dwr.Util;

import uk.ac.open.kmi.dyniqx.wrappers.QueryWrapper;
import uk.ac.open.kmi.dyniqx.wrappers.TermFrequencyWrapper;

public class LuceneStorage implements Storage {

	private String instanceName = null;
	private RAMDirectory idx = new RAMDirectory();
	private Hashtable<String, TreeSet<String>> insertedIDs = new Hashtable<String, TreeSet<String>>();
	private ArrayList<QueryWrapper> queryWrapper = new ArrayList<QueryWrapper>();
	private WebContext wctx;
	
	public Logger logger = Logger.getLogger("uk.ac.open.kmi.dyniqx.storages.LuceneStorage");
	
	public LuceneStorage() {
		this.logger.debug("Storage instance created.");
		
		try {
			IndexWriter writer = new IndexWriter(this.idx, new StandardAnalyzer());
			writer.close();
		} catch (CorruptIndexException e) {
			e.printStackTrace();
		} catch (LockObtainFailedException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		this.logger.debug("Wctx is " + wctx);
	}
		
	@SuppressWarnings("unchecked")
	public synchronized Object performIOAction(int currentAction, Object dataPool) {
		
		if (currentAction == Storage.WRITE_ACTION) {
						
	        try {
        	
	            IndexWriter writer = new IndexWriter(this.idx, new StandardAnalyzer());

	            String className = dataPool.getClass().toString();
	            
	            int addedObjects = 0;
	            String currentQuery = "";
	            String currentSource = "";
	            
	            if (className.equals("class org.apache.lucene.document.Document")) {
	            	Document doc = (Document) dataPool;
	            	String currentID = doc.get("source") + "::" + doc.get("id"); 
	            	
	            	if (this.isInsertedID(currentID) == false) {
	            		writer.addDocument(doc);
	            		addedObjects++;
	            	}
	            	this.addInsertedID(currentID, doc.get("query"));
	            	if (currentQuery.equals("")) currentQuery = doc.get("query");
	            	if (currentSource.equals("")) currentSource = doc.get("source");
	            
	            } else if (className.equals("class java.util.ArrayList")) {
            	
	            	ArrayList<Document> documentArray = (ArrayList<Document>) dataPool;
	            	Iterator iterator = documentArray.iterator();
	            	
	            	while (iterator.hasNext()) {
		            	Document doc = (Document) iterator.next();
		            	String currentID = doc.get("source") + "::" + doc.get("id");

		        		if (this.isInsertedID(currentID) == false) {
	            			writer.addDocument(doc);
	            			addedObjects++;
		            	}
		            	this.addInsertedID(currentID, doc.get("query"));
		            	if (currentQuery.equals("")) currentQuery = doc.get("query");
		            	if (currentSource.equals("")) currentSource = doc.get("source");
	            	}
	            }
	            
	            writer.optimize();
	            writer.close();
	            
	            for (QueryWrapper qw : this.queryWrapper) {
	            	if (qw.getQuery().equals(currentQuery) && qw.getSource().equals(currentSource)) {
	            		qw.setParsedResults(qw.getParsedResults() + addedObjects);
	            	}
	            }

	            return true;
	        }
	        catch(IOException ioe) {
	            ioe.printStackTrace();

	            return false;
	        }
		}

		if ((currentAction == Storage.READ_ACTION)) {
			
            Searcher searcher = null;
            
			try {
				searcher = new IndexSearcher(this.idx);
			} catch (CorruptIndexException e1) {
				e1.printStackTrace();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			
            String queryString = (String) dataPool;
            
            if (queryString.equals("alldocs")) {
            	queryString = "a* b* c* d* e* f* g* h* i* j* k* l* m* n* o* p* q* r* s* t* u* v* w* x* y* z*";
            } else {
	            queryString = queryString.replace("~", " ");
	            queryString = queryString.trim();
	            queryString = queryString.replace("  ", " ");
	            queryString = queryString.replace(" ", " AND ");
            }
                       
            String[] fields = {"title", "description", "summary"};
            BooleanClause.Occur[] flags = new BooleanClause.Occur[] {BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD}; 
            
            Query query;
            Hits hits = null;
            
			try {
				query = MultiFieldQueryParser.parse(queryString, fields, flags, new StandardAnalyzer());
				hits = searcher.search(query);
			} catch (ParseException e1) {
				e1.printStackTrace();
			} catch (IOException e2) {
				e2.printStackTrace();
			}

			ArrayList<Hashtable<String, Object>> al = new ArrayList<Hashtable<String, Object>>(); 
    		Hashtable<String, Object> ht = null;
            
            int hitCount = hits.length();
            
            if (hitCount == 0) {
            	this.logger.info("No matches were found for \"" + queryString + "\"");
            } else {
            	this.logger.info("Hits (" + hitCount + ") for \"" + queryString + "\" were found in quotes by:");

            	if (hitCount > 300) {
            		hitCount = 300;
            	}
            	
                for (int i = 0; i < hitCount; i++) {
                	try {
                		Document doc = hits.doc(i);
               		
                		ht = new Hashtable<String, Object>();
                		ht.put("source", doc.get("source"));
                		ht.put("type", doc.get("type"));
                		ht.put("id", doc.get("id"));
                		ht.put("query", doc.get("query"));
                		ht.put("rank", doc.get("rank"));
                		
                		ht.put("link", doc.get("link"));
                		ht.put("internal", doc.get("internal"));
                		                		
                		ht.put("title", doc.get("title"));
                		ht.put("description", doc.get("description"));
                   		ht.put("date", doc.get("date"));
                		ht.put("authors", doc.get("authors"));
                		ht.put("journal", doc.get("journal"));

                		ht.put("summary", doc.get("summary"));
                		ht.put("width", doc.get("width"));
                   		ht.put("height", doc.get("height"));
                   		ht.put("fileSize", doc.get("fileSize"));
                   		ht.put("fileFormat", doc.get("fileFormat"));
                   		ht.put("thumbUrl", doc.get("thumbUrl"));
                		ht.put("thumbWidth", doc.get("thumbWidth"));
                		ht.put("thumbHeight", doc.get("thumbHeight"));

                		ht.put("score", hits.score(i));
                		
                		al.add(ht);
                	} catch(Exception e) {
                		this.logger.error("Exception caught on document retrieval. No results maybe?");
                		e.printStackTrace();
                	}
                }
            }

            try {
				searcher.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
            
            return al;
	    }
	
		if ((currentAction == Storage.CLEAR_ACTION)) {
			this.setIdx(new RAMDirectory());
			
			this.logger.debug("Index cleared.");
		}
		
		if ((currentAction == Storage.FETCH_ACTION)) {

        	Hashtable<String, Integer> tfvHash = new Hashtable<String, Integer>();
		    String output = "";
		    
			try {
				IndexReader ir;
				ir = IndexReader.open(this.getIdx());
				
				int num = ir.numDocs();
				for ( int i = 0; i < num; i++)	{
					if ( !ir.isDeleted(i)) {
						String target = "";
						if (ir.document(i).get("type").toString().equals("text")) {
							target = "title";
						} else if (ir.document(i).get("type").toString().equals("image")) {
							target = "summary";
						} else if (ir.document(i).get("type").toString().equals("video")) {
							target = "summary";
						} 
						
						TermFreqVector tfv = ir.getTermFreqVector(i, target);
						
						int tfvSize = 0;
						
						if (tfv != null) {
							tfvSize = tfv.size();
							
							String[] tfvTerms = tfv.getTerms();
		    				int[] tfvFreq = tfv.getTermFrequencies();
		    					
		    				for (int j = 0; j < tfvSize; j++) {
		    					if (tfvHash.containsKey(tfvTerms[j])) {
		    						tfvHash.put(tfvTerms[j], tfvHash.get(tfvTerms[j]) + tfvFreq[j]);
		    					} else {
		    						tfvHash.put(tfvTerms[j], tfvFreq[j]);
		    					}
		    				}
						}

					}
				}
				ir.close();
				
				TermFrequencyWrapper tfw = new TermFrequencyWrapper(tfvHash);
				tfw.manageList();
				output = tfw.dumpString();
				
			} catch (CorruptIndexException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}

            return output;
			
		}			
		
		return false;
	}
	
	public void updateGrid() {
			
    	Util utilThis = new Util(this.wctx.getScriptSession());
    	utilThis.setValue("dumping", this.getQueryWrapper().toString());
    	utilThis.addFunctionCall("Dyniqx.updateGrid");
        
    	Integer counter = 0;
    	for (QueryWrapper qw : this.getQueryWrapper()) {
        	counter += qw.getParsedResults();
    	}

    	utilThis.setValue("all-results-counter", counter.toString());
    	utilThis.addFunctionCall("Dyniqx.showNewResults");
    	
	}
		
	public Hashtable<String, TreeSet<String>> getInsertedIDs() {
		return insertedIDs;
	}

	public void setInsertedIDs(Hashtable<String, TreeSet<String>> insertedIDs) {
		this.insertedIDs = insertedIDs;
	}

	public void addInsertedID(String currentID, String queryString) {
		if (this.insertedIDs.containsKey(currentID)) {
			this.insertedIDs.get(currentID).add(queryString);
		} else {
			TreeSet<String> newSet = new TreeSet<String>();
			newSet.add(queryString);
			this.insertedIDs.put(currentID, newSet);
		}
	}
	
	public boolean isInsertedID(String currentID) {
		if (this.insertedIDs.containsKey(currentID)) {
			return true;
		} else {
			return false;
		}
	}
		
	public void setInstanceName(String instanceName) {
		this.instanceName = instanceName;
	}
	
	public String getInstanceName() {
		return this.instanceName;
	}

	public ArrayList<QueryWrapper> getQueryWrapper() {
		return queryWrapper;
	}

	public void setQueryWrapper(ArrayList<QueryWrapper> queryWrapper) {
		this.queryWrapper = queryWrapper;
	}

	public WebContext getWctx() {
		return wctx;
	}

	public void setWctx(WebContext wctx) {
		this.wctx = wctx;
	}

	public RAMDirectory getIdx() {
		return idx;
	}

	public void setIdx(RAMDirectory idx) {
		this.idx = idx;
	}
}
